﻿using System;

namespace PT.Common {
    public struct Rational : IEquatable<Rational>, IComparable<Rational> {
        public Rational( int numerator, int denominator ) : this() {
            Numerator = numerator;
            Denominator = denominator;
        }

        public int Numerator { get; set; }
        public int Denominator { get; set; }

        public static Rational Add(Rational a, Rational b) {
            var lcm = MathEx.LeastCommonMultiple(a.Denominator, b.Denominator);

            int aMult = lcm / a.Denominator, bMult = lcm / b.Denominator;

            return new Rational(a.Numerator * aMult + b.Numerator * bMult, lcm).Reduce();
        }

        public static Rational Subtract(Rational a, Rational b) {
            var lcm = MathEx.LeastCommonMultiple(a.Denominator, b.Denominator);

            int aMult = lcm / a.Denominator, bMult = lcm / b.Denominator;

            return new Rational(a.Numerator * aMult - b.Numerator * bMult, lcm).Reduce();
        }

        public static Rational Multiply(Rational a, Rational b) {
            return new Rational(a.Numerator * b.Numerator, a.Denominator * b.Denominator).Reduce();
        }

        public static Rational Divide(Rational a, Rational b) {
            return new Rational(a.Numerator * b.Denominator, a.Denominator * b.Numerator).Reduce();
        }

        public static Rational Abs(Rational x) {
            var a = new Rational(x.Numerator, x.Denominator);

            if (a.Numerator < 0) {
                a.Numerator *= -1;
            }
            if (a.Denominator < 0) {
                a.Denominator *= -1;
            }

            return a;
        }

        public Rational Reduce() {
            var gcd = MathEx.GreatestCommonDivisor(Numerator, Denominator);

            Numerator /= gcd;
            Denominator /= gcd;

            return this;
        }

        public int Floor() {
            return Numerator / Denominator;
        }

        public static Rational Reduce(Rational a) {
            var gcd = MathEx.GreatestCommonDivisor(a.Numerator, a.Denominator);
            return new Rational(a.Numerator / gcd, a.Denominator / gcd).Reduce();
        }

        public static Rational operator +(Rational a, Rational b) {
            return Add(a, b);
        }

        public static Rational operator -(Rational a, Rational b) {
            return Subtract(a, b);
        }

        public static Rational operator *(Rational a, Rational b) {
            return Multiply(a, b);
        }

        public static Rational operator /(Rational a, Rational b) {
            return Divide(a, b);
        }

        public int CompareTo( Rational other ) {
            // Special cases
            if ( Numerator == other.Numerator ) {
                // The number with smaller denominator is largest
                return -Denominator.CompareTo( other.Denominator );
            }
            if ( Denominator == other.Denominator ) {
                // The number with larger numerator is largest
                return Numerator.CompareTo( other.Numerator );
            }

            int lcm = MathEx.LeastCommonMultiple( Denominator, other.Denominator );
            int tMult = lcm / Denominator, oMult = lcm / other.Denominator;

            return ( tMult * Numerator ).CompareTo( oMult * other.Numerator );
        }

        public bool Equals( Rational other ) {
            return CompareTo( other ) == 0;
        }

        public override bool Equals( object obj ) {
            if (obj is Rational == false ) {
                return false;
            }

            return Equals((Rational)obj);
        }
        public override int GetHashCode() {
            // Use Rational number enumeration formula
            // f(m, n) = (m + n - 1)(m + n - 2)/2 + m
            int n = Numerator, d = Denominator;
            return ( n + d - 1 ) * ( n + d - 2 ) / 2 + d;
        }

        public override string ToString() {
            return string.Format( "{0}/{1}", Numerator, Denominator );
        }

        public static Rational Quarter {
            get { return new Rational(1, 4); }
        }

        public static Rational Eight {
            get { return new Rational(1, 8); }
        }

        public static Rational Sixteenth {
            get { return new Rational(1, 16); }
        }

        public static Rational Thirtysecond {
            get { return new Rational(1, 32); }
        }

        public static Rational EightTriplet {
            get { return new Rational(1, 12); }
        }

        public static Rational SixteenthTriplet {
            get { return new Rational(1, 24); }
        }

        public static Rational ThirtysecondTriplet {
            get { return new Rational(1, 48); }
        }

        public static Rational Zero {
            get { return new Rational(0, 1); }
        }
    }
}